14 设计模式——模板方法模式

返回设计模式博客目录

介绍


模板方法(Template Method)模式:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤

在面向对象开发过程中,通常会遇到这样的一个问题,我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序,但是某些步骤的具体实现是未知的,或者说某些步骤的实现是会随着环境的变化而改变的。

例如,执行程序的流程大致如下:
1)检查代码的正确性;
2)链接相关的类库;
3)编译相关代码;
4)执行程序。

对于不同的程序设计语言,上述 4 个步骤都是不一样的,但是它们的执行流程都是固定的,这类问题的解决方案就是模板方法模式。

优点

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 它在父类中提取了公共的部分代码,便于代码复用。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

缺点

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

使用场景

  • 多个子类有公有的方法,并且逻辑基本相同时。
  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能由各个子类实现。
  • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。

结构与实现


模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。

模式包含以下主要角色。

  • AbstractClass:抽象类,定义了一整套算法框架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
    • 基本方法:是整个算法中的一个步骤,包含以下几种类型。
      • 抽象方法:在抽象类中申明,由具体子类实现。
      • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
      • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  • ConcreteClass:具体实现类,根据需要去实现抽象类中的方法。

其结构图如下图所示。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TemplateMethodPattern {
public static void main(String[] args) {
AbstractClass tm = new ConcreteClass();
tm.TemplateMethod();
}
}
// 抽象类
abstract class AbstractClass {
// 模板方法,不允许覆写
public final void TemplateMethod() {
SpecificMethod();
abstractMethod1();
abstractMethod2();
}
// 具体方法
public void SpecificMethod() {
System.out.println("抽象类中的具体方法被调用...");
}
public abstract void abstractMethod1(); // 抽象方法1
public abstract void abstractMethod2(); // 抽象方法2
}
// 具体子类
class ConcreteClass extends AbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
}

示例


以送快递为例,快递员送快递基本就是一套固定的流程:收到快递 >> 准备派送 >> 联系收货人 >> 确定结果。

定义算法框架,这里是快递员派送快递的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 抽象快递员类
public abstract class Postman {
// 派送流程
// 这里申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序
public final void post() {
prepare(); // 准备派送
call(); // 联系收货人
if (isSign()) { // 是否签收
sign(); // 签收
} else {
refuse(); //拒签
}
}
// 准备操作,固定流程,父类实现
protected void prepare() {
System.out.println("快递已达到,准备派送");
}
// 联系收货人,联系人不一样,所以为抽象方法,子类实现
protected abstract void call();
// 是否签收,这个是钩子方法,用来控制流程的走向
protected boolean isSign() {
return true;
}
// 签收,这个是固定流程,父类实现
protected void sign() {
System.out.println("客户已签收,上报系统");
}
// 拒签,空实现,这个也是钩子方法,子类可以跟进实际来决定是否去实现这个方法
protected void refuse() {
}
}

根据需要去实现抽象类中的方法,下面以派送给两个不同的人为例,其中一个签收,另一个拒收:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 派送给 A 先生
public class PostA extends Postman {
// 联系收货,实现父类的抽象方法
@Override
protected void call() {
System.out.println("联系A先生并送到门口");
}
}
// 派送给 B 先生
public class PostB extends Postman {
// 联系收货,实现父类的抽象方法
@Override
protected void call() {
System.out.println("联系B先生并送到门口");
}
// 是否签收,覆盖父类的钩子方法,控制流程的走向
@Override
protected boolean isSign() {
return false;
}
// 拒签,覆盖父类的钩子方法
@Override
protected void refuse() {
System.out.println("拒绝签收:商品不符");
}
}

客户端测试:

1
2
3
4
5
6
7
8
public void test(){
System.out.println("----派送A----");
Postman postA=new PostA();
postA.post();
System.out.println("----派送B----");
Postman postB=new PostB();
postB.post();
}

输出结果:

1
2
3
4
5
6
7
8
----派送A----
快递已达到,准备派送
联系A先生并送到门口
客户已签收,上报系统
----派送B----
快递已达到,准备派送
联系B先生并送到门口
拒绝签收:商品不符

ANDROID 源码中的实现


在 ANDROID 中,使用模板方法模式的示例有很多。例如:Activity 的生命周期函数、View 的 draw 方法 和 AsyncTask 类。

AsyncTask 的整个执行过程其实是一个框架,具体的实现都需要子类来完成,而且它执行的算法框架是固定的,调用 execute 后会依次执行 onPreExecute、doInBackground、onPostExecute,当然也可以通过 onProgressUpdate 来更新进度。

AsyncTask 的 execute 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}

可以看到 execute 方法是一个 final 方法,它调用了 executeOnExecutor 方法。如果不是 Pending 状态会抛出异常,这也解释了为什么 AsyncTask 只能被执行一次,因为 AsyncTask 的 Running 和 Finished 状态都会抛出异常,因此每次使用 AsyncTask 时都需要重新创建一个对象。

继续往下看,在 executeOnExecutor 方法中首先执行了 onPreExecute 方法,因为 AsyncTask 的要求是需要在 UI 线程中调用 execute 方法。因此,onPreExecute 方法也在 UI 线程中执行,然后将 params 参数传递给 mWorker 对象的 mParams 字段,并且执行了 exec.execute(mFuture) 方法。而 mWorker 和 mFuture 这两个字段都是在构造函数中初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public AsyncTask(@Nullable Looper callbackLooper) {
// 构建一个 Worker 对象
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 调用 doInBackground
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
}
// 代码省略
}
};
}

mWorker 的 call 方法会调用 doInBackground,并且在 finally 方法里面将 result 通过 postResult 方法传递出去。

mFuture 包装了 mWorker 对象,在这个 mFuture 对象的 run 函数中又会调用 mWorker 对象的 call 方法,在 call 方法中调用了 doInBackground 函数。因为 mFuture 提交给了线程池来执行,所以使得 doInBackground 执行在非 UI 线程。得到 doInBackground 的结果后,通过 postResult 传递结果给 UI 线程。

postResult 方法如下:

1
2
3
4
5
6
7
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

postResult 方法把一个消息(MESSAGE_POST_RESULT)发送给 Handler 执行。Handler 是 InternalHandler 类型。当 InternalHandler 接到 MESSAGE_POST_RESULT 类型的消息时,就会调用 result.mTask.finish() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
// 调用了 AsyncTask 的 finish 方法
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
// 结果通过 onPostExecute 回调给用户
onPostExecute(result);
}
mStatus = Status.FINISHED; // 修改状态
}
}

AsyncTask 的 finish 方法又调用了 onPostExecute ,这个时候执行过程就完成了。

总之,execute 方法内部封装了 onPreExecute、doInBackground、onPostExecute 这个逻辑流程,用户可以根据自己的需求再覆写这几个方法,使得用户可以很方便地使用异步任务来完成耗时地操作以及更新UI,这其实就是通过线程池来执行耗时地任务,得到结果之后,通过 Handler 将结果传递到 UI 线程来执行。